- 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>
203 lines
6.5 KiB
TypeScript
203 lines
6.5 KiB
TypeScript
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();
|
|
});
|