Ir al contenido principal

Desarrollo de sistema gerencial (parte 6)

 Elijo opción B (seeders).

Perfecto. Vamos a crear los seeders de datos de prueba. Antes de escribir código, necesito hacerte algunas preguntas para diseñar el conjunto de datos correctamente.


❓ Preguntas para diseñar los seeders

1. Volumen de datos

¿Cuántos registros querés generar? Mi propuesta base:

EntidadCantidad propuesta
Consultorios3
Usuarios (admin)1
Usuarios (gerentes)2
Usuarios (odontólogos)4
Usuarios (recepcionistas)2
Pacientes60
Odontogramas60 (1 por paciente)
Propuestas200
Items de propuesta~600
Tratamientos realizados300
Cuentas60 (1 por paciente)
Movimientos~500
Pagos150

¿Te parecen bien estos números? ¿Ajustamos alguno?


2. Distribución temporal

¿En qué período querés que se distribuyan los datos?

Propuesta: Últimos 14 meses (desde agosto 2025 hasta septiembre 2026), para tener:

  • 12 meses completos de historia

  • Mes actual en curso

  • Mes anterior completo (para comparativas)

¿Te parece bien o preferís otro rango?


3. Relaciones M:M

Recordá que:

  • Un paciente puede estar en varios consultorios

  • Un usuario (odontólogo, gerente) puede estar en varios consultorios

Propuesta:

  • Cada paciente se asigna a 1 o 2 consultorios (aleatorio)

  • Cada odontólogo se asigna a 1 o 2 consultorios

  • Cada gerente se asigna a los 3 consultorios

  • Recepcionistas se asignan a 1 consultorio

¿Te parece bien?


4. Estados de propuestas

Para que el dashboard sea realista, las propuestas deben tener varios estados:

Estado% propuesto
EMITIDA20%
ACEPTADA65%
RECHAZADA15%

¿Te parece bien esta distribución?


5. Datos realistas vs. Faker

Propuesta: Usar Faker para datos básicos (nombres, direcciones, etc.) pero valores controlados para lo clínico:

Campo¿Cómo generarlo?
Nombre, apellidoFaker (locale es_AR)
DocumentoFaker con formato argentino
Fecha de nacimientoFaker entre 1950 y 2020
SexoFaker (male, female)
ProvinciaLista de provincias argentinas
CiudadFaker (city)
TeléfonoFaker con formato argentino
EmailFaker (basado en nombre)
PrestacionesValores reales de tu PrestacionSeeder
PreciosValores realistas en pesos argentinos

¿Te parece bien?


6. Precios de prestaciones

Actualizá los precios base de las prestaciones a valores realistas para Argentina 2026. Mi propuesta:

PrestaciónPrecio base
Extracción$8.000
Obturación pendiente$6.500
Obturación realizada$6.500
Endodoncia (tratamiento de conducto)$25.000
Prótesis$45.000
Implante$180.000
Perno y corona$35.000
Ortodoncia$80.000

¿Te parecen bien? ¿Los ajustamos?


7. Idempotencia

¿Querés que los seeders:

A) Se ejecuten una sola vez (y si ya hay datos, que no hagan nada)
B) Limpien los datos existentes antes de crear nuevos
C) Se puedan ejecutar varias veces sin duplicar (idempotentes)

Mi recomendación: Opción A con un comando --fresh opcional para opción B.


8. Nombres de archivos

Propuesta:

text
database/seeders/Demo/
├── DemoSeeder.php (orquestador)
├── DemoConsultorioSeeder.php
├── DemoUserSeeder.php
├── DemoPacienteSeeder.php
├── DemoOdontogramaSeeder.php
├── DemoPropuestaSeeder.php
├── DemoTratamientoRealizadoSeeder.php
├── DemoCuentaSeeder.php
├── DemoPagoSeeder.php
└── DemoMovimientoSeeder.php

¿Te parece bien?


9. ¿Reemplazar el PrestacionSeeder actual?

¿Querés que reemplacemos los precios actuales de prestaciones (todos en 0.00) por valores realistas? ¿O preferís mantener el PrestacionSeeder actual y crear un seeder aparte?


10. Control de la aleatoriedad

¿Querés que los datos sean deterministas (misma semilla siempre) o aleatorios en cada ejecución?

  • Determinista: útil para pruebas reproducibles

  • Aleatorio: útil para ver variedad

Mi recomendación: Determinista con mt_srand(42).


📋 Resumen de respuestas que necesito

  1. Volumen de datos

  2. Distribución temporal

  3. Relaciones M:M

  4. Estados de propuestas

  5. Datos realistas vs. Faker

  6. Precios de prestaciones

  7. Idempotencia

  8. Nombres de archivos

  9. Reemplazar PrestacionSeeder

  10. Determinismo


¿Respondés las que puedas y las que no las trabajamos juntos?

Respondo: 1. De acuerdo. 2. De acuerdo. 3. De acuerdo.  4. De acuerdo.  5. De acuerdo.  6. Estoy de acuerdo con los valores. No estoy de acuerdo con que haya una prestación obturación pendiente y otra obturación realizada. Para mí, la obturación es una sola y se llama simplemente obturación. Pendiente y realizada son realmente estados de un tratamiento, pero no definen 2 prestaciones distintas. Yo ya hice los cambios en el código de modo que en el odontograma quede en marcas_dentales, código=obturación y haya un booleano “realizado”; y como prestación ahora existe obturación pero ni pendiente ni realizada.  7. Lo tengo que pensar un poco más, porque el tema es que ya tengo seeders de varias entidades que se van a solapar con los seeders que vamos a hacer ahora.  ¿Cómo se maneja una situación así? 8. De acuerdo.  9. Remplazar los valores actuales todos en 0 por precios realistas.  10. Deterministas. ¿Qué es mt_srand(42)?

Excelentes respuestas. Vamos a resolver tus dudas y luego arrancamos.


🔍 Respuesta a tu pregunta 10: ¿Qué es mt_srand(42)?

Es una función de PHP que inicializa el generador de números aleatorios con una semilla fija.

php
mt_srand(42);           // Inicializa el generador con semilla 42
echo mt_rand(1, 100);   // Siempre devuelve el mismo número

¿Por qué importa?

  • Sin semilla: cada vez que ejecutás el seeder, los datos son diferentes

  • Con semilla fija (42): siempre genera los mismos datos, en el mismo orden

  • Faker internamente usa mt_rand(), así que si seteás la semilla antes de usar Faker, los datos son reproducibles

Ventaja: podés debuggear problemas sabiendo que los datos son siempre los mismos.

En Laravel, el paquete fakerphp/faker acepta una semilla opcional:

php
$faker = Faker\Factory::create('es_AR');
$faker->seed(42); // ← Determinista

🔍 Respuesta a tu pregunta 7: Manejo de seeders solapados

Este es un problema real y tiene varias soluciones. Vamos a analizarlo:

El problema

Ya tenés:

  • PrestacionSeeder (crea prestaciones)

  • OdontogramaSeeder (crea odontogramas)

  • Posiblemente otros

Si ahora creamos DemoSeeder, DemoPacienteSeeder, etc., van a solaparse con los existentes.

Opciones de solución

OpciónDescripciónProsContras
A) ReemplazarEliminar los seeders viejos y dejar solo los nuevosLimpioPerdés los datos que ya tengas
B) ConvivirLos nuevos seeders crean datos adicionales sin tocar los viejosNo rompe nadaLos datos quedan mezclados
C) Separar por entornoLos seeders demo solo corren en local y testingLimpio en producciónMás configuración
D) Reemplazar con banderaLos nuevos seeders pueden reemplazar los viejos si se pasa un flagFlexibleMás código

Mi recomendación: Opción C + D combinadas

  1. Los seeders demo solo corren en entorno local y testing

  2. Cada seeder demo es idempotente (verifica si los datos ya existen)

  3. El orquestador DemoSeeder acepta un flag --fresh para limpiar datos demo previos

Estructura:

php
// database/seeders/DemoSeeder.php

public function run(): void
{
    // Solo en local o testing
    if (!app()->environment(['local', 'testing'])) {
        $this->command->warn('Los seeders demo solo se ejecutan en local o testing.');
        return;
    }

    // Si se pasa --fresh, limpiar datos demo previos
    if ($this->command->option('fresh')) {
        $this->limpiarDatosDemo();
    }

    // Ejecutar seeders en orden
    $this->call([
        DemoConsultorioSeeder::class,
        DemoUserSeeder::class,
        DemoPacienteSeeder::class,
        DemoOdontogramaSeeder::class,
        DemoPropuestaSeeder::class,
        DemoTratamientoRealizadoSeeder::class,
        DemoCuentaSeeder::class,
        DemoPagoSeeder::class,
        DemoMovimientoSeeder::class,
    ]);
}

¿Cómo identificar los datos "demo" para limpiarlos?

Hay dos formas:

Opción 1: Campo prueba
Ya tenés pacientes.prueba = 1 para identificar pacientes de prueba. Podés usar el mismo patrón en otras tablas.

Opción 2: Email con dominio especial
Los usuarios demo tienen @demo.local como email. Los pacientes demo tienen @demo.local también.

Mi recomendación: Usar el campo prueba que ya tenés, y replicarlo en todas las tablas demo.


🔍 Respuesta a tu corrección sobre prestaciones

Tenés toda la razón. Es un error conceptual tener dos prestaciones separadas (obturacion-pendiente y obturacion-realizada) cuando en realidad son estados de una misma prestación.

Nuevo modelo:

json
"marcas_dentales": [
    {
        "codigo": "obturacion",
        "casillero": 10,
        "superficie": 1,
        "realizado": false,
        "incluirEnPropuesta": true
    },
    {
        "codigo": "obturacion",
        "casillero": 12,
        "superficie": 2,
        "realizado": true,
        "incluirEnPropuesta": false
    }
]

Y en PrestacionId:

php
enum PrestacionId: string
{
    case PROTESIS = 'protesis';
    case IMPLANTE = 'implante';
    case EXTRACCION = 'extraccion';
    case OBTURACION = 'obturacion';           // ← Solo una
    case ORTODONCIA = 'ortodoncia';
    case PERNO_CORONA = 'perno-corona';
    case TRATAMIENTO_CONDUCTO = 'tratamiento-conducto';
    // case OBTURACION_PENDIENTE = 'obturacion-pendiente';  // ← Eliminar
    // case OBTURACION_REALIZADA = 'obturacion-realizada';  // ← Eliminar
}

Precio: la obturación tiene un solo precio, sin importar el estado.


❓ Preguntas de ajuste fino antes de arrancar

1. Sobre el campo prueba

¿Querés que todas las tablas demo tengan un campo prueba para identificarlas? ¿O preferís que use otro mecanismo (ej: un rango de IDs, o emails con dominio especial)?

Mi propuesta: Agregar prueba a las tablas nuevas que se presten, y usar el prueba existente en pacientes.

2. Sobre PrestacionSeeder

¿Querés que actualice los precios directamente en el PrestacionSeeder existente (y elimine obturacion-pendiente y obturacion-realizada), o preferís que cree un seeder nuevo?

Mi recomendación: Actualizar el existente, es más limpio.

3. Sobre OdontogramaSeeder existente

¿Querés que lo reemplace con uno más realista que genere odontogramas aleatorios para los pacientes demo? ¿O lo dejo como está?

4. Sobre seeders que ya tenés

¿Cuáles son todos los seeders que tenés actualmente? Necesito verlos para evitar solapamientos.

bash
ls -la database/seeders/

📋 Resumen de lo acordado

PuntoDecisión
Volumen✅ Aceptado
Distribución temporal✅ Últimos 14 meses
Relaciones M:M✅ Aceptado
Estados de propuestas✅ 20% emitidas, 65% aceptadas, 15% rechazadas
Datos realistas vs. Faker✅ Mixto (Faker + controlado)
Precios✅ Realistas
Obturación✅ Unificada
Idempotencia⏳ Pendiente definir
Estructura de archivos✅ database/seeders/Demo/
PrestacionSeeder⏳ Pendiente definir
Determinismo✅ Con semilla 42

Contestame las preguntas de ajuste fino (especialmente la lista de seeders actuales) y arrancamos.


Respondo preguntas: 1. Agregar campo prueba a las tablas nuevas que se presten, y usar el campo prueba que ya está en la tabla pacientes. 2. Actualizar el existente. 3. Reemplazalo con uno más realista que genere odontogramas aleatorios para los pacientes demo. 4. ls -la database/seeders/ total 64 drwxrwxr-x 2 gabo gabo 4096 ago 28 17:17 . drwxrwxr-x 5 gabo gabo 4096 ago 31 2025 .. -rw-rw-r-- 1 gabo gabo 588 ago 31 2025 AntecedentesMedicosSeeder.php -rw-rw-r-- 1 gabo gabo 342 sep 10 2025 ConsultorioSeeder.php -rw-rw-r-- 1 gabo gabo 1278 jul 27 13:58 DatabaseSeeder.php -rw-rw-r-- 1 gabo gabo 918 jul 27 22:14 OdontogramaSeeder.php -rw-rw-r-- 1 gabo gabo 517 jul 27 13:58 OdontogramaViejoSeeder.php -rw-rw-r-- 1 gabo gabo 9347 jul 27 13:58 OdontoPiezaViejaSeeder.php -rw-rw-r-- 1 gabo gabo 767 sep 11 2025 PacientesConsultoriosSeeder.php -rw-rw-r-- 1 gabo gabo 656 ago 1 2025 PacienteSeeder.php -rw-rw-r-- 1 gabo gabo 1350 sep 15 10:12 PrestacionSeeder.php -rw-rw-r-- 1 gabo gabo 590 oct 21 2025 RegistroClinicoSeeder.php -rw-rw-r-- 1 gabo gabo 3329 ago 28 17:28 RolePermissionSeeder.php -rw-rw-r-- 1 gabo gabo 869 sep 11 2025 UsuariosConsultoriosSeeder.php Pregunta relacionada ¿En qué consiste un Seeder idempotente?

Buenísimo. Vamos por partes.


🔍 ¿Qué es un Seeder idempotente?

Un seeder idempotente es aquel que, sin importar cuántas veces lo ejecutes, produce el mismo resultado final. No duplica datos, no genera conflictos, no crea basura.

Ejemplo NO idempotente

php
public function run(): void
{
    Consultorio::create([
        'nombre' => 'Consultorio Central',
        'direccion' => 'Av. Siempre Viva 123',
    ]);
}

Si lo ejecutás 2 veces:

  • Primera vez: se crea 1 consultorio

  • Segunda vez: se crea OTRO consultorio idéntico

  • Resultado: 2 consultorios duplicados ❌

Ejemplo idempotente

php
public function run(): void
{
    Consultorio::updateOrCreate(
        ['nombre' => 'Consultorio Central'],  // ← Clave única para buscar
        [                                       // ← Datos a crear/actualizar
            'direccion' => 'Av. Siempre Viva 123',
        ]
    );
}

Si lo ejecutás 2 veces:

  • Primera vez: se crea 1 consultorio

  • Segunda vez: actualiza el consultorio existente

  • Resultado: siempre 1 consultorio ✅

Métodos comunes para lograr idempotencia

MétodoUso
updateOrCreate(['clave' => $valor], [...])Busca por clave, crea o actualiza
firstOrCreate(['clave' => $valor], [...])Busca por clave, crea si no existe
insertOrIgnore([...])Inserta solo si no existe (a nivel SQL)
truncate() + createLimpia todo y recrea (no es idempotente, pero reproducible)

¿Cuándo usar cada uno?

SituaciónMétodo
Datos maestros (prestaciones, roles, permisos)updateOrCreate
Datos demo (pacientes, propuestas)Depende (ver abajo)
Datos que no deben cambiarfirstOrCreate

¿Y para datos demo?

Los datos demo (pacientes, propuestas, etc.) son difíciles de hacer idempotentes porque tienen muchos campos y no siempre tienen una clave natural única.

Soluciones:

  1. Usar un campo prueba = true y limpiar antes de crear

  2. Usar --fresh para limpiar datos demo previos

  3. Usar IDs fijos (ej: IDs del 1000 al 2000 reservados para demo)

Mi recomendación: Opción 2 + 3 combinadas:

  • Los seeders demo usan el campo prueba = true

  • El orquestador limpia datos demo antes de crear (si se pasa --fresh)

  • Los IDs son predecibles


📋 Análisis de tus seeders actuales

Seeder¿Solapa con los nuevos?Acción
AntecedentesMedicosSeederNoMantener
ConsultorioSeederSí (crea consultorios)Reemplazar por DemoConsultorioSeeder
DatabaseSeederOrquestadorActualizar para incluir los nuevos
OdontogramaSeederSí (crea odontogramas)Reemplazar por DemoOdontogramaSeeder
OdontogramaViejoSeederNo (versión vieja)Mantener
OdontoPiezaViejaSeederNo (versión vieja)Mantener
PacientesConsultoriosSeederSí (relación M:M)Integrar en DemoPacienteSeeder
PacienteSeederSí (crea pacientes)Reemplazar por DemoPacienteSeeder
PrestacionSeederSí (crea prestaciones)Actualizar
RegistroClinicoSeederNoMantener
RolePermissionSeederNoMantener
UsuariosConsultoriosSeederSí (relación M:M)Integrar en DemoUserSeeder

🎯 Plan de acción propuesto

1. Actualizar seeders existentes

SeederCambios
PrestacionSeederUnificar obturación, actualizar precios realistas
ConsultorioSeederMantener (útil como seeder base)
PacienteSeederMantener (útil para 1-2 pacientes base)
RolePermissionSeederMantener

2. Crear seeders demo

text
database/seeders/Demo/
├── DemoSeeder.php (orquestador)
├── DemoConsultorioSeeder.php
├── DemoUserSeeder.php
├── DemoPacienteSeeder.php
├── DemoOdontogramaSeeder.php
├── DemoPropuestaSeeder.php
├── DemoTratamientoRealizadoSeeder.php
├── DemoCuentaSeeder.php
├── DemoPagoSeeder.php
└── DemoMovimientoSeeder.php

3. Actualizar DatabaseSeeder

php
public function run(): void
{
    $this->call([
        // Seeders base (siempre)
        RolePermissionSeeder::class,
        PrestacionSeeder::class,
        ConsultorioSeeder::class,
        PacienteSeeder::class,
        AntecedentesMedicosSeeder::class,
        RegistroClinicoSeeder::class,
        
        // Seeders demo (solo en local/testing)
        DemoSeeder::class,
    ]);
}

4. Modificar el DemoSeeder para que limpie

php
// database/seeders/Demo/DemoSeeder.php

public function run(): void
{
    if (!app()->environment(['local', 'testing'])) {
        $this->command->warn('Los seeders demo solo se ejecutan en local o testing.');
        return;
    }

    // Limpiar datos demo previos
    $this->limpiarDatosDemo();

    // Ejecutar seeders en orden
    $this->call([
        DemoConsultorioSeeder::class,
        DemoUserSeeder::class,
        DemoPacienteSeeder::class,
        DemoOdontogramaSeeder::class,
        DemoPropuestaSeeder::class,
        DemoTratamientoRealizadoSeeder::class,
        DemoCuentaSeeder::class,
        DemoPagoSeeder::class,
        DemoMovimientoSeeder::class,
    ]);
}

private function limpiarDatosDemo(): void
{
    // Eliminar en orden inverso (para respetar foreign keys)
    \App\Models\Movimiento::whereHas('cuenta.paciente', fn($q) => $q->where('prueba', true))->delete();
    \App\Models\Pago::whereHas('paciente', fn($q) => $q->where('prueba', true))->delete();
    \App\Models\Cuenta::whereHas('paciente', fn($q) => $q->where('prueba', true))->delete();
    \App\Models\TratamientoRealizado::whereHas('paciente', fn($q) => $q->where('prueba', true))->delete();
    \App\Models\TratamientoPropuestaItem::whereHas('propuesta.paciente', fn($q) => $q->where('prueba', true))->delete();
    \App\Models\TratamientoPropuesta::whereHas('paciente', fn($q) => $q->where('prueba', true))->delete();
    \App\Models\Odontograma::whereHas('paciente', fn($q) => $q->where('prueba', true))->delete();
    \App\Models\Paciente::where('prueba', true)->delete();
    \App\Models\User::where('email', 'like', '%@demo.local')->delete();
}

❓ Preguntas antes de arrancar

1. Sobre el dominio del email de usuarios demo

¿Te parece bien usar @demo.local para los usuarios demo? ¿O preferís otro dominio (ej: @test.local, @example.com)?

2. Sobre el campo prueba en tablas nuevas

¿Qué tablas querés que tengan prueba?

Mi propuesta:

  • users (agregar)

  • pacientes (ya existe)

  • consultorios (agregar)

  • tratamiento_propuestas (agregar)

  • tratamientos_realizados (agregar)

  • pagos (agregar)

  • cuentas (agregar)

  • movimientos (agregar)

3. Sobre la eliminación de ConsultorioSeeder, PacienteSeeder, PacientesConsultoriosSeeder y UsuariosConsultoriosSeeder

¿Querés eliminarlos (porque los demo los reemplazan) o mantenerlos como seeders base para producción?

Mi recomendación: Mantenerlos como seeders base (útiles para producción), y que los demo agreguen datos adicionales.


Contestame estas 3 preguntas y arrancamos con el primer seeder.


Respondo preguntas: 1. @demo.local. 2. Estoy de acuerdo con todo, haciendo la aclaración de que la tabla consultorios ya tiene el campo prueba. 3. Mantenerlos, y que los demo agreguen datos adicionales.

Perfecto. Con estas respuestas, ya podemos arrancar. Aquí iría una serie de procedimientos técnicos y material de codebase.

Comentarios