feat: add database seed with demo data and Docker Compose setup
- Seed creates demo tenant, users (admin, contador, visor) - Creates tenant schema with cfdis, iva_mensual, alertas tables - Inserts 50 demo CFDIs and 6 months of IVA records - Docker Compose with PostgreSQL, API, and Web services - Demo credentials: admin/contador/visor @demo.com / demo123 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
202
apps/api/prisma/seed.ts
Normal file
202
apps/api/prisma/seed.ts
Normal file
@@ -0,0 +1,202 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import bcrypt from 'bcryptjs';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function main() {
|
||||
console.log('🌱 Seeding database...');
|
||||
|
||||
// Create demo tenant
|
||||
const schemaName = 'tenant_ede123456ab1';
|
||||
|
||||
const tenant = await prisma.tenant.upsert({
|
||||
where: { rfc: 'EDE123456AB1' },
|
||||
update: {},
|
||||
create: {
|
||||
nombre: 'Empresa Demo SA de CV',
|
||||
rfc: 'EDE123456AB1',
|
||||
plan: 'professional',
|
||||
schemaName,
|
||||
cfdiLimit: 2000,
|
||||
usersLimit: 10,
|
||||
},
|
||||
});
|
||||
|
||||
console.log('✅ Tenant created:', tenant.nombre);
|
||||
|
||||
// Create demo users
|
||||
const passwordHash = await bcrypt.hash('demo123', 12);
|
||||
|
||||
const users = [
|
||||
{ email: 'admin@demo.com', nombre: 'Admin Demo', role: 'admin' as const },
|
||||
{ email: 'contador@demo.com', nombre: 'Contador Demo', role: 'contador' as const },
|
||||
{ email: 'visor@demo.com', nombre: 'Visor Demo', role: 'visor' as const },
|
||||
];
|
||||
|
||||
for (const userData of users) {
|
||||
const user = await prisma.user.upsert({
|
||||
where: { email: userData.email },
|
||||
update: {},
|
||||
create: {
|
||||
tenantId: tenant.id,
|
||||
email: userData.email,
|
||||
passwordHash,
|
||||
nombre: userData.nombre,
|
||||
role: userData.role,
|
||||
},
|
||||
});
|
||||
console.log(`✅ User created: ${user.email} (${user.role})`);
|
||||
}
|
||||
|
||||
// Create tenant schema
|
||||
await prisma.$executeRawUnsafe(`CREATE SCHEMA IF NOT EXISTS "${schemaName}"`);
|
||||
|
||||
// Create tables in tenant schema
|
||||
await prisma.$executeRawUnsafe(`
|
||||
CREATE TABLE IF NOT EXISTS "${schemaName}"."cfdis" (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
uuid_fiscal VARCHAR(36) UNIQUE NOT NULL,
|
||||
tipo VARCHAR(20) NOT NULL,
|
||||
serie VARCHAR(25),
|
||||
folio VARCHAR(40),
|
||||
fecha_emision TIMESTAMP NOT NULL,
|
||||
fecha_timbrado TIMESTAMP NOT NULL,
|
||||
rfc_emisor VARCHAR(13) NOT NULL,
|
||||
nombre_emisor VARCHAR(300) NOT NULL,
|
||||
rfc_receptor VARCHAR(13) NOT NULL,
|
||||
nombre_receptor VARCHAR(300) NOT NULL,
|
||||
subtotal DECIMAL(18,2) NOT NULL,
|
||||
descuento DECIMAL(18,2) DEFAULT 0,
|
||||
iva DECIMAL(18,2) DEFAULT 0,
|
||||
isr_retenido DECIMAL(18,2) DEFAULT 0,
|
||||
iva_retenido DECIMAL(18,2) DEFAULT 0,
|
||||
total DECIMAL(18,2) NOT NULL,
|
||||
moneda VARCHAR(3) DEFAULT 'MXN',
|
||||
tipo_cambio DECIMAL(10,4) DEFAULT 1,
|
||||
metodo_pago VARCHAR(3),
|
||||
forma_pago VARCHAR(2),
|
||||
uso_cfdi VARCHAR(4),
|
||||
estado VARCHAR(20) DEFAULT 'vigente',
|
||||
xml_url TEXT,
|
||||
pdf_url TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
`);
|
||||
|
||||
// Create IVA monthly table
|
||||
await prisma.$executeRawUnsafe(`
|
||||
CREATE TABLE IF NOT EXISTS "${schemaName}"."iva_mensual" (
|
||||
id SERIAL PRIMARY KEY,
|
||||
año INT NOT NULL,
|
||||
mes INT NOT NULL,
|
||||
iva_trasladado DECIMAL(18,2) NOT NULL,
|
||||
iva_acreditable DECIMAL(18,2) NOT NULL,
|
||||
iva_retenido DECIMAL(18,2) DEFAULT 0,
|
||||
resultado DECIMAL(18,2) NOT NULL,
|
||||
acumulado DECIMAL(18,2) NOT NULL,
|
||||
estado VARCHAR(20) DEFAULT 'pendiente',
|
||||
fecha_declaracion TIMESTAMP,
|
||||
UNIQUE(año, mes)
|
||||
)
|
||||
`);
|
||||
|
||||
// Insert demo CFDIs
|
||||
const cfdiTypes = ['ingreso', 'egreso'];
|
||||
const rfcs = ['XAXX010101000', 'MEXX020202000', 'AAXX030303000', 'BBXX040404000'];
|
||||
const nombres = ['Cliente Demo SA', 'Proveedor ABC', 'Servicios XYZ', 'Materiales 123'];
|
||||
|
||||
for (let i = 0; i < 50; i++) {
|
||||
const tipo = cfdiTypes[i % 2];
|
||||
const rfcIndex = i % 4;
|
||||
const subtotal = Math.floor(Math.random() * 50000) + 1000;
|
||||
const iva = subtotal * 0.16;
|
||||
const total = subtotal + iva;
|
||||
|
||||
await prisma.$executeRawUnsafe(`
|
||||
INSERT INTO "${schemaName}"."cfdis"
|
||||
(uuid_fiscal, tipo, serie, folio, fecha_emision, fecha_timbrado,
|
||||
rfc_emisor, nombre_emisor, rfc_receptor, nombre_receptor,
|
||||
subtotal, iva, total, estado)
|
||||
VALUES (
|
||||
'${crypto.randomUUID()}',
|
||||
'${tipo}',
|
||||
'A',
|
||||
'${1000 + i}',
|
||||
NOW() - INTERVAL '${Math.floor(Math.random() * 180)} days',
|
||||
NOW() - INTERVAL '${Math.floor(Math.random() * 180)} days',
|
||||
'${tipo === 'ingreso' ? 'EDE123456AB1' : rfcs[rfcIndex]}',
|
||||
'${tipo === 'ingreso' ? 'Empresa Demo SA de CV' : nombres[rfcIndex]}',
|
||||
'${tipo === 'egreso' ? 'EDE123456AB1' : rfcs[rfcIndex]}',
|
||||
'${tipo === 'egreso' ? 'Empresa Demo SA de CV' : nombres[rfcIndex]}',
|
||||
${subtotal},
|
||||
${iva},
|
||||
${total},
|
||||
'vigente'
|
||||
)
|
||||
ON CONFLICT (uuid_fiscal) DO NOTHING
|
||||
`);
|
||||
}
|
||||
|
||||
console.log('✅ Demo CFDIs created');
|
||||
|
||||
// Create IVA monthly records
|
||||
let acumulado = 0;
|
||||
|
||||
for (let mes = 1; mes <= 6; mes++) {
|
||||
const trasladado = Math.floor(Math.random() * 100000) + 150000;
|
||||
const acreditable = Math.floor(Math.random() * 80000) + 120000;
|
||||
const resultado = trasladado - acreditable;
|
||||
acumulado += resultado;
|
||||
|
||||
await prisma.$executeRawUnsafe(`
|
||||
INSERT INTO "${schemaName}"."iva_mensual"
|
||||
(año, mes, iva_trasladado, iva_acreditable, resultado, acumulado, estado)
|
||||
VALUES (2024, ${mes}, ${trasladado}, ${acreditable}, ${resultado}, ${acumulado},
|
||||
'${mes <= 4 ? 'declarado' : 'pendiente'}')
|
||||
ON CONFLICT (año, mes) DO NOTHING
|
||||
`);
|
||||
}
|
||||
|
||||
console.log('✅ IVA monthly records created');
|
||||
|
||||
// Create alerts table
|
||||
await prisma.$executeRawUnsafe(`
|
||||
CREATE TABLE IF NOT EXISTS "${schemaName}"."alertas" (
|
||||
id SERIAL PRIMARY KEY,
|
||||
tipo VARCHAR(50) NOT NULL,
|
||||
titulo VARCHAR(200) NOT NULL,
|
||||
mensaje TEXT NOT NULL,
|
||||
prioridad VARCHAR(20) DEFAULT 'media',
|
||||
fecha_vencimiento TIMESTAMP,
|
||||
leida BOOLEAN DEFAULT FALSE,
|
||||
resuelta BOOLEAN DEFAULT FALSE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
`);
|
||||
|
||||
await prisma.$executeRawUnsafe(`
|
||||
INSERT INTO "${schemaName}"."alertas" (tipo, titulo, mensaje, prioridad, fecha_vencimiento)
|
||||
VALUES
|
||||
('iva_favor', 'IVA a Favor Disponible', 'Tienes $43,582.40 de IVA a favor acumulado', 'media', NULL),
|
||||
('declaracion', 'Declaración Mensual', 'La declaración mensual de IVA/ISR vence el 17 de febrero', 'alta', NOW() + INTERVAL '5 days'),
|
||||
('discrepancia', 'CFDI con Discrepancia', 'Se encontraron 12 facturas con discrepancias', 'alta', NULL)
|
||||
ON CONFLICT DO NOTHING
|
||||
`);
|
||||
|
||||
console.log('✅ Alerts created');
|
||||
|
||||
console.log('🎉 Seed completed successfully!');
|
||||
console.log('\n📝 Demo credentials:');
|
||||
console.log(' Admin: admin@demo.com / demo123');
|
||||
console.log(' Contador: contador@demo.com / demo123');
|
||||
console.log(' Visor: visor@demo.com / demo123');
|
||||
}
|
||||
|
||||
main()
|
||||
.catch((e) => {
|
||||
console.error('Error seeding database:', e);
|
||||
process.exit(1);
|
||||
})
|
||||
.finally(async () => {
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
Reference in New Issue
Block a user